from operator import xor
from DESConfig import IPTable, ExtensionTable, KeyPermutationTable1, KeyPermutationTable2, KeyShiftTable, SBox, P

# Data from book 
bidata = [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
encryptionKey = [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1]

# data from doc
# bidata = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1]
# encryptionKey = [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1]


def Permute(rawData, PermutationTable, isDebug=False):
	result = []
	i = 0
	for c in PermutationTable:
		if isDebug:
			print "{0} = rawData[{1}] / {2}".format(i, c, rawData[c-1])
			i += 1
		result.append(rawData[c-1])

	return result


def InversePermute(rawData, PermutationTable, isDebug=False):
	result = rawData[:]  #copy rawData
	i = 0
	for c in PermutationTable:
		if isDebug:
			print "{0} -> {1}".format(rawData[i], c-1)
		result[c-1] = rawData[i]
		i+=1

	return result


def ShiftSubKey(perviousSubKey, currentTurn):
	keyShift = KeyShiftTable[currentTurn-1]
	keyLength = len(perviousSubKey)
	# separated key as left and right
	leftKey = perviousSubKey[:keyLength/2]
	rightKey = perviousSubKey[keyLength/2:]
	# Shift
	shiftedLeftKey = leftKey[keyShift:] + leftKey[:keyShift]
	shiftedRightKey = rightKey[keyShift:] + rightKey[:keyShift]
	return shiftedLeftKey + shiftedRightKey


def GenerateSubKeys(originalKey, totalTurns, isDebug=False):
	intermediateKey = []
	result = []
	for t in range(totalTurns+1):
		if t < 1:
			# for round 0: 
			#	shrink original key 64bit -> 58bit
			#	permute according to PC-1
			intermediateKey = Permute(originalKey, KeyPermutationTable1)
			result.append(intermediateKey)
		else:
			# shfit according to shift table
			# The result is input data for next round
			intermediateKey = ShiftSubKey(intermediateKey, t)
			
			# Permute PC-2
			resultForCurrentTurn = Permute(intermediateKey, KeyPermutationTable2)
			result.append(resultForCurrentTurn)

			if isDebug:
				print "Round {0} Key {1}. Its length is {2}".format(t, BinaryListToHex(resultForCurrentTurn), len(resultForCurrentTurn))
				print "{0} - {1}".format(''.join(str(x) for x in resultForCurrentTurn[:len(resultForCurrentTurn)/2]), ''.join(str(x) for x in resultForCurrentTurn[len(resultForCurrentTurn)/2:]))

		if isDebug:
				print "Round {0} intermediateKey {1}. Its length is {2}".format(t, BinaryListToHex(intermediateKey), len(intermediateKey))
				print "{0} - {1}".format(''.join(str(x) for x in intermediateKey[:len(intermediateKey)/2]), ''.join(str(x) for x in intermediateKey[len(intermediateKey)/2:]))
				print "********************************"

	return result


def PermutateSBox(xorResult, box, isDebug = False):
	x = int(str(xorResult[0]) + str(xorResult[-1]), 2)
	y = int(''.join(str(c) for c in xorResult[1:-1]), 2)

	hex_str = '{0:04b}'.format(box[x][y])

	result = []
	for c in hex_str:
		result.append(int(c))

	if isDebug:
		print "Box[{0}][{1}]: {2} => {3}".format(x, y, box[x][y], hex_str)

	return result


def F(previousRight, subkey, isDebug = False):
	# extend right block from 32 bits to 48 bits
	extendedRight = Permute(previousRight, ExtensionTable)

	# key xor ExtendedRight
	xorResult = []
	length = len(extendedRight)
	for i in range(length):
		xorResult.append(xor(extendedRight[i], subkey[i]))

	# separated xor result 6 bits each group
	total6BitGroup = length / 6
	sboxResult = []
	for i in range(total6BitGroup):
		sboxResult += (PermutateSBox(xorResult[i*6 : (i+1)*6], SBox[i]))

	permuteResult = Permute(sboxResult, P)

	if isDebug:
		print "       E(R) = {0}".format(''.join(str(c) for c in extendedRight))
		print " K xor E(R) = {0}".format(''.join(str(c) for c in xorResult))
		print "SBox result = {0}".format(''.join(str(c) for c in sboxResult))

	return permuteResult


def EncodeRightHalf(previousResult, encryptionKey, isDebug = False):
	length = len(previousResult)

	# F function
	fResult = F(previousResult[length/2 : ], encryptionKey, isDebug)

	xorResult = []
	for i in range(length/2):
		xorResult.append(xor(fResult[i], previousResult[i]))

	if isDebug:
		print "       Left = {0}".format(''.join(str(c) for c in previousResult[:length/2]))
		print "    f(R, K) = {0}".format(''.join(str(c) for c in fResult))

	return xorResult


def Encrypt(plainInput, encryptionKey, totalRounds = 16, isDebug = False):
	length = len(plainInput)

	#init: generate subkey for each iteration
	keyList = GenerateSubKeys(encryptionKey, totalRounds)

	intermediateResult = []
	
	for i in range(totalRounds + 2):
		if isDebug:
			print "Current Round {0}".format(i)
			if i == 0:
				print "L{0}: {1} R{0}: {2} ".format(i, ''.join(str(c) for c in intermediateResult[:length/2]), ''.join(str(c) for c in intermediateResult[length/2:]))
			else:
				print "L{0}: {1} R{0}: {2} ".format(i-1, ''.join(str(c) for c in intermediateResult[:length/2]), ''.join(str(c) for c in intermediateResult[length/2:]))
			if (0 < i) and (i < 17):
				print "K{0}: {1}".format(i, ''.join(str(c) for c in keyList[i]))
				print "K{0}: {1}".format(i, ''.join(str(c) for c in BinaryListToHex(keyList[i])))
			print '---------------------------'

		# round 0: permute on plain input
		if i < 1:
			intermediateResult = Permute(plainInput, IPTable)
		elif i == 17:
			intermediateResult = intermediateResult[length/2:] + intermediateResult[:length/2]
		else:
			# L1=R0, R1=L0+f(R0, k1)
			intermediateResult = intermediateResult[length/2:] + EncodeRightHalf(intermediateResult, keyList[i], isDebug)

		if isDebug:
			if (0 < i) and (i < 17):
				print "L + f(R, k) = {0}".format(''.join(str(c) for c in intermediateResult[length/2:]))
			print '==========================='


	return InversePermute(intermediateResult, IPTable)




def BinaryStringToHex(binary_str):
	return hex(int(binary_str, 2))


def BinaryListToHex(binaryList):
	binary_str = ''.join(str(x) for x in binaryList)
	return BinaryStringToHex(binary_str)





if __name__ == '__main__':
	# my_hexdata = "1e030f03080d2930"
	# scale = 16 ## equals to hexadecimal
	# num_of_bits = 8
	# print bin(int(my_hexdata, scale))#[2:].zfill(num_of_bits)
	
	# print BinaryListToHex(bidata)

	# ip_result = Permute(bidata, IPTable)
	# print "{0} - {1}".format(''.join(str(x) for x in ip_result[:len(ip_result)/2]), ''.join(str(x) for x in ip_result[len(ip_result)/2:]))
	# print '-------------------------'

	#restoredData = InversePermute(ip_result, IPTable)
	#print BinaryListToHex(restoredData[:len(restoredData)/2])
	#print BinaryListToHex(restoredData[len(restoredData)/2:])

	print 'Start calculate keys for each round:'
	keyList = GenerateSubKeys(encryptionKey, 16, True)
	print '-------------------------'

	print 'Start calculate SBox permutation:'
	tmp = PermutateSBox([0,1,1,0,0,0], SBox[0], True)
	print ''.join(str(x) for x in tmp)
	print '-------------------------'

	print 'Start calculate F(R, k):'
	tmp = F([1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, ],
		[0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, ], True)
	print "F(R, K) = {0}".format(''.join(str(c) for c in tmp))
	print '-------------------------'

	print 'Start calculate EncodeRightHalf(previousResult, Key):'
	tmp = EncodeRightHalf([1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, ],
		[0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, ],
		True)
	print "L + f(R, K) = {0}".format(''.join(str(c) for c in tmp))
	print ''
	print '+'*100
	print ''

	print 'Encrypt function result:'
	tmp = Encrypt(bidata, encryptionKey, isDebug = True)
	print 'DES Encryption result:'
	print ''.join(str(x) for x in tmp)
	print ''.join(str(x) for x in BinaryListToHex(tmp))
	print '-------------------------'
